/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 */
/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <unistd.h>
#include <sys/stat.h>
#include <fstream>
#include <fcntl.h>
#include <hilog/log.h>
#include <sys/stat.h>
#include <ohaudio/native_audiorenderer.h>
#include <ohaudio/native_audiostreambuilder.h>
#include "Recorder.h"
#include "AVCodecSampleError.h"
#include "RecorderNative.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0xFF00
#define LOG_TAG "recorder"

namespace {
constexpr int32_t MINUS_ONE = -1;
constexpr int32_t ZERO = 0;
constexpr int32_t ONE = 1;
constexpr int32_t TWO = 2;
constexpr int32_t THREE = 3;
constexpr int32_t FOUR = 4;
constexpr int32_t FIVE = 5;
constexpr int32_t EIGHT = 8;
constexpr int32_t ONE_HUNDRED_TWENTY_EIGHT = 128;
constexpr double ZERO_TWO = 0.02;
constexpr int32_t DEFAULT_FILE_PERMISSIONS = S_IRUSR | S_IWUSR;
constexpr int32_t DEFAULT_FILE_FLAGS = O_WRONLY | O_CREAT | O_TRUNC;
constexpr int32_t NAPI_AUTO_LENGTH_VALUE = -1;
constexpr int32_t DEFAULT_VIDEO_WIDTH = 1920;
constexpr int32_t DEFAULT_VIDEO_HEIGHT = 1080;
constexpr int32_t DEFAULT_FRAME_RATE = 60;
constexpr int32_t DEFAULT_VIDEO_BITRATE = 2500000;
constexpr int32_t DEFAULT_AUDIO_CHANNELS = 1;
constexpr int32_t DEFAULT_AUDIO_BITRATE = 64000;
constexpr int32_t DEFAULT_SAMPLE_RATE = 44100;
constexpr int32_t YUV_CONVERSION_R_COEFF = 66;
constexpr int32_t YUV_CONVERSION_G_COEFF = 129;
constexpr int32_t YUV_CONVERSION_B_COEFF = 25;
constexpr int32_t YUV_CONVERSION_UV_R_COEFF1 = -38;
constexpr int32_t YUV_CONVERSION_UV_G_COEFF1 = -74;
constexpr int32_t YUV_CONVERSION_UV_B_COEFF1 = 112;
constexpr int32_t YUV_CONVERSION_UV_R_COEFF2 = 112;
constexpr int32_t YUV_CONVERSION_UV_G_COEFF2 = -94;
constexpr int32_t YUV_CONVERSION_UV_B_COEFF2 = -18;
constexpr int32_t YUV_CONVERSION_SHIFT = 8;
constexpr int32_t YUV_CONVERSION_Y_OFFSET = 16;
constexpr int32_t YUV_CONVERSION_UV_OFFSET = 128;
}

struct AsyncCallbackInfo {
    napi_env env;
    napi_async_work asyncWork;
    napi_deferred deferred;
    int32_t resultCode = ZERO;
    std::ofstream outputFile;
    std::string surfaceId = "";
    std::string filePath = "";
    SampleInfo sampleInfo;
};

void SafeCloseFile(AsyncCallbackInfo* asyncCallbackInfo)
{
    if (asyncCallbackInfo->sampleInfo.outputFd != MINUS_ONE) {
        close(asyncCallbackInfo->sampleInfo.outputFd);
        asyncCallbackInfo->sampleInfo.outputFd = MINUS_ONE;
    }
    if (asyncCallbackInfo->outputFile.is_open()) {
        asyncCallbackInfo->outputFile.close();
    }
}

void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code)
{
    asyncCallbackInfo->resultCode = code;
}

int  InitializeOutputFile(AsyncCallbackInfo* asyncCallbackInfo)
{
    asyncCallbackInfo->filePath = asyncCallbackInfo->sampleInfo.inputFilePath;
    int fd = open(asyncCallbackInfo->filePath.c_str(), DEFAULT_FILE_FLAGS, DEFAULT_FILE_PERMISSIONS);
    if (fd < ZERO) {
        int err = errno;
        OH_LOG_ERROR(LOG_APP, "Failed to open file '%{public}s': %{public}s (errno=%{public}d)",
            asyncCallbackInfo->filePath.c_str(), strerror(err), err);
        return MINUS_ONE;
    }
    return fd;
}

void DealCallBackImpl(napi_env env, napi_status status, AsyncCallbackInfo* asyncCallbackInfo)
{
    napi_value code;
    napi_create_int32(env, asyncCallbackInfo->resultCode, &code);
    napi_value surfaceId;
    napi_create_string_utf8(env, asyncCallbackInfo->surfaceId.data(), NAPI_AUTO_LENGTH_VALUE, &surfaceId);
    napi_value obj;
    napi_create_object(env, &obj);
    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "surfaceId", surfaceId);
    napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
    napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
    delete asyncCallbackInfo;
}

void DealCallBack(napi_env env, napi_status status, void* data)
{
    DealCallBackImpl(env, status, static_cast<AsyncCallbackInfo*>(data));
}

bool ParseConfig(napi_env env, napi_callback_info info, SampleInfo& sampleInfo)
{
    size_t argc = ONE;
    napi_value args[ONE] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_valuetype valueType;
    napi_typeof(env, args[ZERO], &valueType);
    if (valueType != napi_object) {
        return false;
    }
    napi_value configObj = args[ZERO];
    napi_value value;
    napi_get_named_property(env, configObj, "width", &value);
    napi_get_value_int32(env, value, &sampleInfo.videoWidth);
    napi_get_named_property(env, configObj, "height", &value);
    napi_get_value_int32(env, value, &sampleInfo.videoHeight);
    napi_get_named_property(env, configObj, "fps", &value);
    napi_get_value_double(env, value, &sampleInfo.frameRate);
    napi_get_named_property(env, configObj, "videoBitrate", &value);
    napi_get_value_int32(env, value, &sampleInfo.videoBitrate);
    napi_get_named_property(env, configObj, "audioChannels", &value);
    napi_get_value_int32(env, value, &sampleInfo.audioChannelCount);
    napi_get_named_property(env, configObj, "audioBitrate", &value);
    napi_get_value_int32(env, value, &sampleInfo.audioBitRate);
    napi_get_named_property(env, configObj, "sampleRate", &value);
    napi_get_value_int32(env, value, &sampleInfo.audioSampleRate);
    napi_get_named_property(env, configObj, "filepath", &value);
    size_t str_length = ZERO;
    napi_get_value_string_utf8(env, value, nullptr, ZERO, &str_length);
    sampleInfo.inputFilePath.resize(str_length + ONE);
    size_t copied = ZERO;
    napi_get_value_string_utf8(env, value, &sampleInfo.inputFilePath[ZERO], sampleInfo.inputFilePath.size(), &copied);
    sampleInfo.inputFilePath.resize(copied);
    return true;
}

napi_value RecorderNative::Init(napi_env env, napi_callback_info info)
{
    SampleInfo sampleInfo;
    if (!ParseConfig(env, info, sampleInfo)) {
        return nullptr;
    }
    if (sampleInfo.videoWidth == ZERO || sampleInfo.videoHeight == ZERO) {
        sampleInfo.videoWidth = DEFAULT_VIDEO_WIDTH;
        sampleInfo.videoHeight = DEFAULT_VIDEO_HEIGHT;
        sampleInfo.frameRate = DEFAULT_FRAME_RATE;
        sampleInfo.videoBitrate = DEFAULT_VIDEO_BITRATE;
    }
    if (sampleInfo.audioChannelCount == ZERO || sampleInfo.audioBitRate == ZERO || sampleInfo.audioSampleRate == ZERO) {
        sampleInfo.audioChannelCount = DEFAULT_AUDIO_CHANNELS;
        sampleInfo.audioBitRate = DEFAULT_AUDIO_BITRATE;
        sampleInfo.audioSampleRate = DEFAULT_SAMPLE_RATE;
    }
    sampleInfo.audioCodecMime = OH_AVCODEC_MIMETYPE_AUDIO_AAC;
    sampleInfo.videoCodecMime = OH_AVCODEC_MIMETYPE_VIDEO_AVC;
    sampleInfo.audioSampleForamt = OH_BitsPerSample::SAMPLE_S16LE;
    sampleInfo.audioMaxInputSize = sampleInfo.audioSampleRate * ZERO_TWO * sampleInfo.audioChannelCount * sizeof(short);
    if (sampleInfo.audioChannelCount  == ONE) {
            sampleInfo.audioChannelLayout = OH_AudioChannelLayout::CH_LAYOUT_MONO;
    } else if (sampleInfo.audioChannelCount  == TWO) {
        sampleInfo.audioChannelLayout = OH_AudioChannelLayout::CH_LAYOUT_STEREO;
    }
    napi_value promise;
    napi_deferred deferred;
    napi_create_promise(env, &deferred, &promise);
    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;
    asyncCallbackInfo->resultCode = MINUS_ONE;
    asyncCallbackInfo->sampleInfo = sampleInfo;
    SafeCloseFile(asyncCallbackInfo);
    int fd = InitializeOutputFile(asyncCallbackInfo);
    asyncCallbackInfo->sampleInfo.outputFd = fd;
    int32_t ret = Recorder::GetInstance().Init(asyncCallbackInfo->sampleInfo);
    if (ret == AVCODEC_SAMPLE_ERR_OK) {
        Recorder::GetInstance().Start();
    }
    napi_create_int64(env, ONE, &promise);
    return promise;
}

napi_value RecorderNative::PushAudioData(napi_env env, napi_callback_info info)
{
    size_t argc = TWO;
    napi_value args[TWO];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    void* data;
    size_t length;
    napi_get_arraybuffer_info(env, args[ZERO], &data, &length);
    int64_t timestamp;
    napi_get_value_int64(env, args[ONE], &timestamp);
    
    int32_t result = Recorder::GetInstance().PushAudioData(
        static_cast<uint8_t*>(data), length, timestamp);
    napi_value napiResult;
    napi_create_int32(env, result, &napiResult);
    return napiResult;
}

std::vector<uint8_t> RGBAToNV12(const uint8_t* rgbaData, int32_t width, int32_t height)
{
    std::vector<uint8_t> nv12Data;
    nv12Data.reserve(width * height * THREE / TWO);
    nv12Data.resize(width * height * THREE / TWO);
    
    uint8_t* yPlane = nv12Data.data();
    uint8_t* uvPlane = yPlane + width * height;
    
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int rgbaIndex = (y * width + x) * FOUR;
            uint8_t r = rgbaData[rgbaIndex];
            uint8_t g = rgbaData[rgbaIndex + ONE];
            uint8_t b = rgbaData[rgbaIndex + TWO];
            
            int yIndex = y * width + x;
            yPlane[yIndex] = static_cast<uint8_t>(
                (YUV_CONVERSION_R_COEFF * r + YUV_CONVERSION_G_COEFF * g + YUV_CONVERSION_B_COEFF * b +
                 ONE_HUNDRED_TWENTY_EIGHT) >> YUV_CONVERSION_SHIFT) + YUV_CONVERSION_Y_OFFSET;
            
            if (y % TWO == 0 && x % TWO == 0) {
                int uvIndex = (y / TWO) * (width / TWO) + (x / TWO);
                uint8_t u = static_cast<uint8_t>(
                    (YUV_CONVERSION_UV_R_COEFF1 * r + YUV_CONVERSION_UV_G_COEFF1 * g + YUV_CONVERSION_UV_B_COEFF1 * b +
                     ONE_HUNDRED_TWENTY_EIGHT) >> YUV_CONVERSION_SHIFT) + YUV_CONVERSION_UV_OFFSET;
                uint8_t v = static_cast<uint8_t>(
                    (YUV_CONVERSION_UV_R_COEFF2 * r + YUV_CONVERSION_UV_G_COEFF2 * g + YUV_CONVERSION_UV_B_COEFF2 * b +
                     ONE_HUNDRED_TWENTY_EIGHT) >> YUV_CONVERSION_SHIFT) + YUV_CONVERSION_UV_OFFSET;
                
                uvPlane[uvIndex * TWO] = u;
                uvPlane[uvIndex * TWO + ONE] = v;
            }
        }
    }
    return nv12Data;
}

napi_value RecorderNative::PushVideoData(napi_env env, napi_callback_info info)
{
    size_t argc = THREE;
    napi_value args[THREE];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (argc < THREE) {
        napi_throw_error(env, nullptr, "Wrong number of arguments");
        return nullptr;
    }
    void* data;
    size_t length;
    if (napi_get_arraybuffer_info(env, args[ZERO], &data, &length) != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to get array buffer info");
        return nullptr;
    }
    OH_LOG_INFO(LOG_APP, "Received video data: ptr=%{public}p, length=%{public}zu", data, length);
    int64_t timestamp;
    if (napi_get_value_int64(env, args[ONE], &timestamp) != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to get timestamp");
        return nullptr;
    }
    bool isKeyFrame;
    if (napi_get_value_bool(env, args[TWO], &isKeyFrame) != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to get key frame flag");
        return nullptr;
    }
    int32_t width = Recorder::GetInstance().GetVideoWidth();
    int32_t height = Recorder::GetInstance().GetVideoHeight();
    std::vector<uint8_t> yuvData = RGBAToNV12(static_cast<uint8_t*>(data), width, height);
    OH_LOG_INFO(LOG_APP, "Received video data: yuvData.size()=%{public}ld", yuvData.size());
    int32_t result = Recorder::GetInstance().PushVideoData(
        yuvData.data(), yuvData.size(), timestamp, isKeyFrame);
    napi_value napiResult;
    napi_create_int32(env, result, &napiResult);
    return napiResult;
}

void NativeStop(napi_env env, AsyncCallbackInfo *asyncCallbackInfo)
{
    int32_t ret = Recorder::GetInstance().Stop();
    if (ret != AVCODEC_SAMPLE_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, MINUS_ONE);
    }
    SafeCloseFile(asyncCallbackInfo);
    SetCallBackResult(asyncCallbackInfo, ZERO);
}

void NativeStopWrapper(napi_env env, void *data)
{
    NativeStop(env, static_cast<AsyncCallbackInfo*>(data));
}

napi_value RecorderNative::Stop(napi_env env, napi_callback_info info)
{
    napi_value promise;
    napi_deferred deferred;
    napi_create_promise(env, &deferred, &promise);
    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo();
    asyncCallbackInfo->env = env;
    asyncCallbackInfo->asyncWork = nullptr;
    asyncCallbackInfo->deferred = deferred;

    napi_value resourceName;
    napi_create_string_latin1(env, "recorder", NAPI_AUTO_LENGTH_VALUE, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName,
        NativeStopWrapper,
        DealCallBack,
        asyncCallbackInfo,
        &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

napi_value RecorderNative::Release(napi_env env, napi_callback_info info)
{
    Recorder::GetInstance().Release();
    napi_value result;
    napi_get_undefined(env, &result);
    return result;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor classProp[] = {
        {"initNative", nullptr, RecorderNative::Init, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pushAudioData", nullptr, RecorderNative::PushAudioData, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"pushVideoData", nullptr, RecorderNative::PushVideoData, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopNative", nullptr, RecorderNative::Stop, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"disposeNative", nullptr, RecorderNative::Release, nullptr, nullptr, nullptr, napi_default, nullptr},
    };

    napi_value RecorderNative = nullptr;
    const char *classBindName = "recorderNative";
    napi_define_class(env, classBindName, strlen(classBindName), nullptr, nullptr, FIVE, classProp, &RecorderNative);
    napi_define_properties(env, exports, sizeof(classProp) / sizeof(classProp[ZERO]), classProp);
    return exports;
}
EXTERN_C_END

static napi_module RecorderModule = {
    .nm_version = ONE,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "recorder",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterRecorderModule(void) { napi_module_register(&RecorderModule); }